package cn.b.sky.shop.activiti.service.impl;

import cn.b.sky.shop.activiti.model.condition.ProcessDefinitionCondition;
import cn.b.sky.common.exception.ImeException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.activiti.bpmn.converter.BpmnXMLConverter;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.editor.constants.ModelDataJsonConstants;
import org.activiti.editor.language.json.converter.BpmnJsonConverter;
import org.activiti.engine.*;
import org.activiti.engine.delegate.Expression;
import org.activiti.engine.history.HistoricProcessInstance;
import org.activiti.engine.identity.User;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.bpmn.behavior.UserTaskActivityBehavior;
import org.activiti.engine.impl.persistence.entity.ProcessDefinitionEntity;
import org.activiti.engine.impl.pvm.delegate.ActivityBehavior;
import org.activiti.engine.impl.pvm.process.ActivityImpl;
import org.activiti.engine.impl.task.TaskDefinition;
import org.activiti.engine.repository.*;
import org.activiti.engine.runtime.Execution;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.task.Task;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
import java.util.zip.ZipInputStream;

/**
 * 流程引擎基类
 * 
 * @author lidapeng
 * 
 */
@Service("evunProcessEngine")
public class EvunProcessEngine {

  private static final Logger LOGGER = LoggerFactory.getLogger(EvunProcessEngine.class);

  @Autowired
  private RuntimeService runtimeService;

  @Autowired
  private TaskService taskService;

  @Autowired
  private RepositoryService repositoryService;

  @Autowired
  private IdentityService identityService;

  @Autowired
  private HistoryService historyService;

  /**
   * 发布流程
   */
  public Deployment deployProcess(ZipInputStream stream, String name) {
    if (!StringUtils.isBlank(name)) {
      return repositoryService.createDeployment().name(name).addZipInputStream(stream).deploy();
    }
    return null;
  }

  /**
   * <strong>Title : convertProcessDefinitionToModel<br>
   * </strong> <strong>Description : </strong>流程发布包转换为BPMNModel<br>
   * <strong>Create on : 2014-10-15下午2:57:45<br>
   * </strong>
   * <p>
   * 
   * @param deploymentId
   * @return
   * @throws
   * @author lidapeng<br>
   * @version <strong>v1.0.0</strong><br>
   * <br>
   *          <strong>修改历史:</strong><br>
   *          修改人 | 修改日期 | 修改描述<br>
   *          -------------------------------------------<br>
   * <br>
   * <br>
   */
  public void convertProcessDefinitionToModel(String deploymentId, String category) {
    if (deploymentId == null) {
      return;
    }
    ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().deploymentId(deploymentId)
        .singleResult();
    try {
      InputStream bpmnStream = repositoryService.getResourceAsStream(deploymentId, processDefinition.getResourceName());
      XMLInputFactory xif = XMLInputFactory.newInstance();
      InputStreamReader in = new InputStreamReader(bpmnStream, "UTF-8");
      XMLStreamReader xtr = xif.createXMLStreamReader(in);
      BpmnModel bpmnModel = new BpmnXMLConverter().convertToBpmnModel(xtr);

      if (bpmnModel.getMainProcess() == null || bpmnModel.getMainProcess().getId() == null) {
        LOGGER.warn("流程id:" + processDefinition.getId() + "生成流程模型失败,确定这文件包含有效的BPMN");
      } else {

        if (bpmnModel.getLocationMap().size() == 0) {
          LOGGER.warn("流程id:" + processDefinition.getId() + "生成流程模型失败,文件中没有找到BMMN DI");
        } else {

          BpmnJsonConverter converter = new BpmnJsonConverter();
          ObjectNode modelNode = converter.convertToJson(bpmnModel);
          Model modelData = repositoryService.newModel();

          ObjectNode modelObjectNode = new ObjectMapper().createObjectNode();
          modelObjectNode.put(ModelDataJsonConstants.MODEL_NAME, processDefinition.getName());
          modelObjectNode.put(ModelDataJsonConstants.MODEL_DESCRIPTION, processDefinition.getDescription());

          modelData.setCategory(processDefinition.getName());

          modelData.setKey(processDefinition.getKey());
          List<Model> models = repositoryService.createModelQuery().modelKey(processDefinition.getKey())
              .orderByModelVersion().desc().list();
          if (models != null && models.size() > 0) {
            Integer version = models.get(0).getVersion() + 1;
            modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, models.get(0).getVersion() + 1);
            modelData.setName(processDefinition.getName() + "[" + version + "]");
          } else {
            modelObjectNode.put(ModelDataJsonConstants.MODEL_REVISION, 1);
            modelData.setName(processDefinition.getName() + "[1]");
          }
          modelData.setCategory(category);
          modelData.setMetaInfo(modelObjectNode.toString());
          repositoryService.saveModel(modelData);

          repositoryService.addModelEditorSource(modelData.getId(), modelNode.toString().getBytes("utf-8"));
        }
      }
    } catch (Exception e) {
      LOGGER.error("流程id:" + processDefinition.getId() + "生成流程模型失败", e);
    }
  }

  /**
   * <strong>Title : startProcess<br>
   * </strong> <strong>Description : </strong>通过流程key启动流程<br>
   * <strong>Create on : 2014-10-9上午9:08:21<br>
   * </strong>
   * <p>
   * 
   * @param variables
   * @param processKey
   * @return ProcessInstance
   * @throws
   * @author lidapeng<br>
   * @version <strong>v1.0.0</strong><br>
   * <br>
   *          <strong>修改历史:</strong><br>
   *          修改人 | 修改日期 | 修改描述<br>
   *          -------------------------------------------<br>
   * <br>
   * <br>
   */
  public ProcessInstance startProcess(Map<String, Object> variables, String processKey) {

    return runtimeService.startProcessInstanceByKey(processKey, variables);
  }

  public ProcessInstance startProcessByKeyAndTenantId(Map<String, Object> variables, String processKey, String tenantId) {

    try {
      return runtimeService.startProcessInstanceByKeyAndTenantId(processKey, variables, tenantId);
    } catch (ActivitiException e) {
      if (e.getMessage().indexOf("no processes deployed with key") != -1) {
        LOGGER.warn("没有部署流程!", e);
        throw ImeException.newException("noWorkFlowKey.C", processKey.split(","));
      } else if (e.getCause().getCause().getCause() instanceof ImeException) {
        ImeException ie = (ImeException) e.getCause().getCause().getCause();
        throw ie;
      }
      return null;
    }
  }

  /**
   * <strong>Title : getProcessDefinitionList<br>
   * </strong> <strong>Description : </strong>根据条件获取已经部署的流程<br>
   * <strong>Create on : 2014-10-7下午2:22:55<br>
   * </strong>
   * <p>
   * 
   * @param query
   * @param islast
   * @return List<ProcessDefinition>
   * @throws
   * @author lidapeng<br>
   * @version <strong>v1.0.0</strong><br>
   * <br>
   *          <strong>修改历史:</strong><br>
   *          修改人 | 修改日期 | 修改描述<br>
   *          -------------------------------------------<br>
   * <br>
   * <br>
   */
  public List<ProcessDefinition> getProcessDefinitionList(ProcessDefinitionCondition query, Boolean islast) {
    ProcessDefinitionQuery pquery = repositoryService.createProcessDefinitionQuery();
    //添加租户概念
    pquery.processDefinitionTenantIdLike(query.getTenantId());
    if (!StringUtils.isBlank(query.getCategory())) {
      pquery = pquery.processDefinitionCategoryLike(query.getCategory());
    }
    if (!StringUtils.isBlank(query.getName())) {
      pquery = pquery.processDefinitionNameLike(query.getName());
    }
    if (!StringUtils.isBlank(query.getKey())) {
      pquery = pquery.processDefinitionKey(query.getKey());
    }
    if (islast != null) {
      pquery = pquery.latestVersion();
    }
    // 按照类别排序
    pquery.orderByProcessDefinitionId().desc();
    return pquery.listPage(query.getStartRow() - 1, query.getEndRow() - 1);
  }

  /**
   * 
   * <strong>Title : getProcessDefinitionsCount<br></strong>
   * <strong>Description : </strong>获取流程定义列表<br> 
   * <strong>Create on : 2015-12-3 下午1:48:57<br></strong>
   * <p>
   * @param query
   * @param islast
   * @return
   * long
   * @throws 
   * @author lidapeng<br>
   * @version <strong>v1.0.0</strong><br>
   * <br>
   * <strong>修改历史:</strong><br>
   * 修改人 | 修改日期 | 修改描述<br>
   * -------------------------------------------<br>
   * <br>
   * <br>
   */
  public long getProcessDefinitionsCount(ProcessDefinitionCondition query, Boolean islast) {
    ProcessDefinitionQuery pquery = repositoryService.createProcessDefinitionQuery();
    //添加租户概念
    pquery.processDefinitionTenantIdLike(query.getTenantId());
    if (!StringUtils.isBlank(query.getCategory())) {
      pquery = pquery.processDefinitionCategoryLike(query.getCategory());
    }
    if (!StringUtils.isBlank(query.getName())) {
      pquery = pquery.processDefinitionNameLike(query.getName());
    }
    if (!StringUtils.isBlank(query.getKey())) {
      pquery = pquery.processDefinitionKey(query.getKey());
    }
    if (islast != null) {
      pquery = pquery.latestVersion();
    }
    return pquery.count();
  }

  /**
   * <strong>Title : getProcessDefinitionById<br>
   * </strong> <strong>Description : </strong>(通过id获取ProcessDefinition)<br>
   * <strong>Create on : 2014-10-8上午8:42:24<br>
   * </strong>
   * <p>
   * 
   * @param id
   * @return ProcessDefinition
   * @throws
   * @author lidapeng<br>
   * @version <strong>v1.0.0</strong><br>
   * <br>
   *          <strong>修改历史:</strong><br>
   *          修改人 | 修改日期 | 修改描述<br>
   *          -------------------------------------------<br>
   * <br>
   * <br>
   */
  public ProcessDefinition getProcessDefinitionById(String id) {

    if (StringUtils.isBlank(id)) {
      return null;
    }
    ProcessDefinitionQuery pquery = repositoryService.createProcessDefinitionQuery();
    pquery = pquery.processDefinitionId(id);
    return pquery.singleResult();
  }

  /**
   * 
   * <strong>Title : getProcessDefinitionByDmentId<br>
   * </strong> <strong>Description : </strong>通过发布流程的id查询流程定义<br>
   * <strong>Create on : 2015-11-4 下午5:09:08<br>
   * </strong>
   * <p>
   * 
   * @param deploymentId
   * @return List<ProcessDefinition>
   * @throws
   * @author lidapeng<br>
   * @version <strong>v1.0.0</strong><br>
   * <br>
   *          <strong>修改历史:</strong><br>
   *          修改人 | 修改日期 | 修改描述<br>
   *          -------------------------------------------<br>
   * <br>
   * <br>
   */
  public List<ProcessDefinition> getProcessDefinitionByDmentId(String deploymentId) {

    if (StringUtils.isBlank(deploymentId)) {
      return null;
    }
    return repositoryService.createProcessDefinitionQuery().deploymentId(deploymentId).list();
  }

  /**
   * <strong>Title : getProcessDefinitionImg<br>
   * </strong> <strong>Description : </strong>流程图获取<br>
   * <strong>Create on : 2014-10-9上午8:28:23<br>
   * </strong>
   * <p>
   * 
   * @param deploymentId
   * @param resourceName
   * @return InputStream
   * @throws
   * @author lidapeng<br>
   * @version <strong>v1.0.0</strong><br>
   * <br>
   *          <strong>修改历史:</strong><br>
   *          修改人 | 修改日期 | 修改描述<br>
   *          -------------------------------------------<br>
   * <br>
   * <br>
   */
  public InputStream getProcessDefinitionImg(String deploymentId, String diagramResourceName) {

    if (StringUtils.isBlank(deploymentId)) {
      return null;
    }

    return repositoryService.getResourceAsStream(deploymentId, diagramResourceName);
  }

  /**
   * <strong>Title : getDeployMentById<br>
   * </strong> <strong>Description : </strong>(通过id获取流程发布)<br>
   * <strong>Create on : 2014-10-9上午8:29:00<br>
   * </strong>
   * <p>
   * 
   * @param deploymentId
   * @return Deployment
   * @throws
   * @author lidapeng<br>
   * @version <strong>v1.0.0</strong><br>
   * <br>
   *          <strong>修改历史:</strong><br>
   *          修改人 | 修改日期 | 修改描述<br>
   *          -------------------------------------------<br>
   * <br>
   * <br>
   */
  public Deployment getDeployMentById(String deploymentId) {
    if (StringUtils.isBlank(deploymentId)) {
      return null;
    }
    DeploymentQuery query = repositoryService.createDeploymentQuery();
    query = query.deploymentId(deploymentId);
    return query.singleResult();
  }

  /**
   * 
   * <strong>Title : getProcessInstanceById<br>
   * </strong> <strong>Description : </strong>获取流程实例，运行中的流程<br>
   * <strong>Create on : 2015-11-3 下午3:21:49<br>
   * </strong>
   * <p>
   * 
   * @param processInstanceId
   * @return ProcessInstance
   * @throws
   * @author lidapeng<br>
   * @version <strong>v1.0.0</strong><br>
   * <br>
   *          <strong>修改历史:</strong><br>
   *          修改人 | 修改日期 | 修改描述<br>
   *          -------------------------------------------<br>
   * <br>
   * <br>
   */
  public ProcessInstance getProcessInstanceById(String processInstanceId) {
    if (!StringUtils.isBlank(processInstanceId)) {
      return runtimeService.createProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
    } else {
      return null;
    }
  }

  public HistoricProcessInstance getHistoricProcessInstanceById(String processInstanceId) {
    if (!StringUtils.isBlank(processInstanceId)) {
      return historyService.createHistoricProcessInstanceQuery().processInstanceId(processInstanceId).singleResult();
    } else {
      return null;
    }
  }

  /**
   * 流程跟踪图
   * 
   * @param processInstanceId
   *            流程实例ID
   * @return 封装了各种节点信息
   */
  public List<Map<String, Object>> traceProcess(String processInstanceId) {
    Execution execution = runtimeService.createExecutionQuery().executionId(processInstanceId).singleResult();// 执行实例
    Object property = execution.getActivityId();
    String activityId = "";
    if (property != null) {
      activityId = property.toString();
    }
    ProcessInstance processInstance = this.getProcessInstanceById(processInstanceId);
    ProcessDefinitionEntity processDefinition = (ProcessDefinitionEntity) ((RepositoryServiceImpl) repositoryService)
        .getDeployedProcessDefinition(processInstance.getProcessDefinitionId());
    List<ActivityImpl> activitiList = processDefinition.getActivities();// 获得当前任务的所有节点

    List<Map<String, Object>> activityInfos = new ArrayList<Map<String, Object>>();
    for (ActivityImpl activity : activitiList) {

      boolean currentActiviti = false;
      String id = activity.getId();

      // 当前节点
      if (id.equals(activityId)) {
        currentActiviti = true;
      }

      Map<String, Object> activityImageInfo = packageSingleActivitiInfo(activity, processInstance, currentActiviti);

      activityInfos.add(activityImageInfo);
    }

    return activityInfos;
  }

  /**
   * 封装输出信息，包括：当前节点的X、Y坐标、变量信息、任务类型、任务描述
   * 
   * @param activity
   * @param processInstance
   * @param currentActiviti
   * @return
   */
  private Map<String, Object> packageSingleActivitiInfo(ActivityImpl activity, ProcessInstance processInstance,
      boolean currentActiviti) {
    Map<String, Object> vars = new HashMap<String, Object>();
    Map<String, Object> activityInfo = new HashMap<String, Object>();
    activityInfo.put("currentActiviti", currentActiviti);
    setPosition(activity, activityInfo);
    setWidthAndHeight(activity, activityInfo);

    Map<String, Object> properties = activity.getProperties();
    vars.put("taskType", properties.get("type").toString());

    ActivityBehavior activityBehavior = activity.getActivityBehavior();
    LOGGER.debug("activityBehavior={}", activityBehavior);
    if (activityBehavior instanceof UserTaskActivityBehavior) {

      Task currentTask = null;

      /*
       * 当前节点的task
       */
      if (currentActiviti) {
        currentTask = getCurrentTaskInfo(processInstance);
      }

      /*
       * 当前任务的分配角色
       */
      UserTaskActivityBehavior userTaskActivityBehavior = (UserTaskActivityBehavior) activityBehavior;
      TaskDefinition taskDefinition = userTaskActivityBehavior.getTaskDefinition();
      Set<Expression> candidateGroupIdExpressions = taskDefinition.getCandidateGroupIdExpressions();
      if (!candidateGroupIdExpressions.isEmpty()) {

        // 任务的处理角色
        setTaskGroup(vars, candidateGroupIdExpressions);

        // 当前处理人
        if (currentTask != null) {
          setCurrentTaskAssignee(vars, currentTask);
        }
      }
    }

    vars.put("nodeDesc", properties.get("documentation"));

    String description = activity.getProcessDefinition().getDescription();
    vars.put("memo", description);

    LOGGER.debug("trace variables: {}", vars);
    activityInfo.put("vars", vars);
    return activityInfo;
  }

  /**
   * 设置宽度、高度属性
   * 
   * @param activity
   * @param activityInfo
   */
  private void setWidthAndHeight(ActivityImpl activity, Map<String, Object> activityInfo) {
    activityInfo.put("width", activity.getWidth());
    activityInfo.put("height", activity.getHeight());
  }

  /**
   * 设置坐标位置
   * 
   * @param activity
   * @param activityInfo
   */
  private void setPosition(ActivityImpl activity, Map<String, Object> activityInfo) {
    activityInfo.put("x", activity.getX());
    activityInfo.put("y", activity.getY());
  }

  /**
   * 获取当前节点信息
   * 
   * @param processInstance
   * @return
   */
  private Task getCurrentTaskInfo(ProcessInstance processInstance) {
    Task currentTask = null;
    try {
      String activitiId = processInstance.getActivityId();
      LOGGER.debug("current activity id: {}", activitiId);

      currentTask = taskService.createTaskQuery().processInstanceId(processInstance.getId())
          .taskDefinitionKey(activitiId).singleResult();
      LOGGER.debug("current task for processInstance: {}", ToStringBuilder.reflectionToString(currentTask));

    } catch (Exception e) {
      LOGGER.error("can not get property activityId from processInstance: {}", e);
    }
    return currentTask;
  }

  private void setTaskGroup(Map<String, Object> vars, Set<Expression> candidateGroupIdExpressions) {
    String roles = "";
    for (Expression expression : candidateGroupIdExpressions) {
      String expressionText = expression.getExpressionText();
      String roleName = identityService.createGroupQuery().groupId(expressionText).singleResult().getName();
      roles += roleName;
    }
    vars.put("taskRole", roles);
  }

  /**
   * 设置当前处理人信息
   * 
   * @param vars
   * @param currentTask
   */
  private void setCurrentTaskAssignee(Map<String, Object> vars, Task currentTask) {
    String assignee = currentTask.getAssignee();
    if (assignee != null) {
      User assigneeUser = identityService.createUserQuery().userId(assignee).singleResult();
      String userInfo = assigneeUser.getFirstName() + " " + assigneeUser.getLastName();
      vars.put("当前处理人", userInfo);
    }
  }
}
